from copy import deepcopy
from cy_query.db import transaction
from cy_query.db.utils import CreateModelDataError
from asgiref.sync import sync_to_async,markcoroutinefunction


__all__ = ['CreateModelData','UpdateModelData','UpdateModelData2']


class BaseModelData:
    def __init__(self,model,data,using='default',savepoint=True,durable=False,pop_pk=True, **kwargs):
        self.model = model
        self.data = deepcopy(data)
        self.using = using
        self.savepoint = savepoint
        self.durable = durable
        self.pop_pk = pop_pk
        self.kwargs = kwargs


class CreateModelData(BaseModelData):
    def models_fields(self, fields: dict):
        from cy_query.db.models.base import Model
        from cy_query.db.models.fields.related import ForwardManyToOneDescriptor, ManyToManyDescriptor, \
            ReverseManyToOneDescriptor
        dic = {}
        array = []
        children_dict = {}
        for k, v in fields.items():
            if hasattr(v,'tolist'):
                v = v.tolist()
            if hasattr(self.model, k):
                field = getattr(self.model, k)
                if field is None:
                    continue
                if isinstance(field, ForwardManyToOneDescriptor):
                    if k.find('_id') == -1:
                        if not isinstance(v, Model):
                            k = '%s_id' % k
                elif isinstance(field, ManyToManyDescriptor):
                    array.append((k, v if isinstance(v, list) else [v]))
                    continue
                if isinstance(field, ReverseManyToOneDescriptor):
                    children_dict[(k,field.rel)] = v
                    continue
                dic[k] = v
        return dic,array,children_dict

    def create(self,data=None):
        data = self.data or data
        if hasattr(data,'tolist'):
            data = data.tolist()
        if isinstance(data, dict):
            if self.pop_pk:
                data.pop('id',None)
            dic,array,children_dict = self.models_fields(data)
            if not array and not children_dict:
                return self.model.objects.using(self.using).create(**dic)
            with transaction.atomic(using=self.using, savepoint=self.savepoint, durable=self.durable):
                obj = self.model.objects.using(self.using).create(**dic)
                for i in array:
                    field = getattr(obj, i[0])
                    for index in i[1]:
                        field.add(index)
                for (k,field),v in children_dict.items():
                    [i.update({field.field.name:obj}) for i in v]
                    CreateModelData(field.related_model,v,using=self.using,savepoint=self.savepoint,durable=self.durable,pop_pk=self.pop_pk,**self.kwargs).create()
                return obj
        if isinstance(data, list):
            array = []
            objs = []
            with transaction.atomic(using=self.using,savepoint=self.savepoint, durable=self.durable):
                for i in data:
                    if self.pop_pk:
                        i.pop('id',None)
                    dic,arr,children_dict = self.models_fields(i)
                    if not arr and not children_dict:
                        array.append(self.model(**dic))
                    else:
                        obj = self.model.objects.using(self.using).create(**dic)
                        for item in arr:
                            field = getattr(obj, item[0])
                            for index in item[1]:
                                field.add(index)
                        for (k, field), v in children_dict.items():
                            if isinstance(v,dict):
                                v[field.field.name] = obj
                            else:
                                [i.update({field.field.name: obj}) for i in v]
                            CreateModelData(field.related_model, v,using=self.using,savepoint=self.savepoint,durable=self.durable,pop_pk=self.pop_pk,**self.kwargs).create()
                        objs.append(obj)
            if array:
                objs += self.model.objects.using(self.using).bulk_create(array)
            return objs
        raise CreateModelDataError("不支持其他传入类型,当前类型:%s" % type(data))

    async def acreate(self,data=None):
        if not self.data and not data:
            return None
        return await sync_to_async(self.create)(data)

    markcoroutinefunction(acreate)


class UpdateModelData(BaseModelData):
    # 批量修改保存
    def update(self, data=None):
        data = self.data or data
        if hasattr(data,'tolist'):
            data = data.tolist()
        if isinstance(data, dict):
            pk = data.pop('id')
            return self.model.objects.filter(id=pk).update(**CreateModelData.models_fields(self, data)[0])
        self.models = list(data)
        update_fields = set()
        for index, i in enumerate(self.models):
            if isinstance(i, self.model):
                for field in self.model._meta.fields if i.pk else []:
                    value = getattr(i, field.name)
                    if i._instance.get(field.name) != value:
                        update_fields.add(field.name)
            elif isinstance(i, dict):
                if not i.get('id'):
                    raise ValueError("%s is not a %s 'id'" % i)
                _data = CreateModelData.models_fields(self, i)[0]
                if not isinstance(_data, dict):
                    raise ValueError("存在的多对多无法进行批量修改")
                [update_fields.add(k) for k in list(_data.keys()) if k != 'id']
                self.models[index] = self.model(**_data)
            else:
                raise ValueError('%s is not a %s instance or dict' % (i, self.model.__name__))
        return self.model.objects.using(self.using).bulk_update(self.models, fields=update_fields)

    async def aupdate(self, data=None):
        if not self.data and not data:
            return None
        return await sync_to_async(self.update)(data)

    markcoroutinefunction(aupdate)


class UpdateModelData2(BaseModelData):
    """支持嵌套层次批量修改，子删除，新增等操作"""

    @transaction.atomic
    def update(self,data=None):
        data = self.data or data
        if not data:
            return data
        self.update_nested_objects(self.model, data)

    async def aupdate(self,data):
        return await sync_to_async(self.update)(data)

    markcoroutinefunction(aupdate)

    def update_nested_objects(self,model_class, data_list, parent_field=None, parent_instance=None, relations_map=None):
        """
        递归更新嵌套对象，支持自定义 related_name 和层级深度
        :param model_class: 当前层级的模型类
        :param data_list: 当前层级的数据列表
        :param parent_field: 指向父对象的外键字段名
        :param parent_instance: 父对象实例
        :param relations_map: 关系映射字典 {字段名: {'model': 子模型类, 'fk_field': 子模型中外键字段名}}
        :return: 更新的实例列表
        """
        from cy_query.db.models import Model
        if isinstance(data_list,dict):
            data_list = [data_list]
        updated_instances = []
        current_ids = []

        # 自动构建关系映射（如果未提供）
        if relations_map is None:
            relations_map = {}
            # 分析模型的所有反向关系
            for rel in model_class._meta.related_objects:
                if rel.one_to_many or rel.one_to_one:
                    rel_name = rel.get_accessor_name()
                    relations_map[rel_name] = {
                        'model': rel.related_model,
                        'fk_field': rel.field.name
                    }

        for data in data_list:
            item_data = data.copy()

            # 识别并提取关系字段
            relations = {}
            for key in list(item_data.keys()):
                # 如果在关系映射中，或者是标准的反向关系字段（_set结尾）
                if key in relations_map or (isinstance(item_data[key], list) and key.endswith('_set')):
                    relations[key] = item_data.pop(key)

            # 处理ManyToMany字段
            m2m_data = {}
            for field in model_class._meta.many_to_many:
                if field.name in item_data:
                    m2m_data[field.name] = item_data.pop(field.name)

            # 更新或创建当前对象
            if 'id' in item_data and item_data['id']:
                # 更新现有对象
                if m2m_data:
                    model_class.objects.filter(pk=item_data.pop('id')).update(**item_data)
                else:
                    instance = model_class.objects.only('id').get(id=item_data.pop('id'))
                    dic,array,children_dict = CreateModelData(model_class,None).models_fields(item_data)
                    for attr, value in dic.items():
                        setattr(instance, attr, value)
                    instance.save()
                    current_ids.append(instance.id)
            else:
                # 创建新对象
                if parent_field and parent_instance:
                    item_data[parent_field] = parent_instance.id if isinstance(parent_instance,
                                                                               Model) else parent_instance
                instance = CreateModelData(model_class,item_data,using=self.using,savepoint=self.savepoint,durable=self.durable,pop_pk=self.pop_pk,**self.kwargs).create()
                # instance = model_class.objects.create_model_data(item_data,request=self.request)
                current_ids.append(instance.id)

            # 处理ManyToMany关系
            for field_name, values in m2m_data.items():
                getattr(instance, field_name).set(values)

            # 递归更新子对象
            for rel_field, rel_data in relations.items():
                rel_info = relations_map.get(rel_field)

                if rel_info:
                    # 使用映射的关系信息
                    child_model = rel_info['model']
                    child_fk_field = rel_info['fk_field']
                else:
                    # 自动推断关系（尝试获取反向关系）
                    try:
                        rel_descriptor = getattr(model_class, rel_field)
                        child_model = rel_descriptor.related.related_model
                        child_fk_field = rel_descriptor.field.name
                        # 缓存映射信息
                        relations_map[rel_field] = {
                            'model': child_model,
                            'fk_field': child_fk_field
                        }
                    except AttributeError:
                        # 无法识别的关系，跳过
                        continue

                # 递归更新
                self.update_nested_objects(
                    child_model,
                    rel_data,
                    child_fk_field,
                    instance,
                    relations_map.get(rel_field, {}).get('relations_map')
                )

            updated_instances.append(instance)

        # 删除多余对象（仅当有父实例时）
        if parent_instance and parent_field:
            # 获取当前父实例下所有子对象的ID
            filter_kwargs = {
                parent_field: parent_instance.id if isinstance(parent_instance, Model) else parent_instance}
            existing_ids = set(model_class.objects.filter(**filter_kwargs).values_list('id', flat=True))

            # 计算需要删除的ID
            ids_to_delete = existing_ids - set(current_ids)

            if ids_to_delete:
                model_class.objects.filter(id__in=ids_to_delete).delete()#update(is_delete=True)

        return updated_instances
